Migrate configurator/upgrade wizard from Apache Click to Jakarta Servlets + FreeMarker#1062
Conversation
…k to FreeMarker transition
Review summarySolid, carefully-executed migration — retires the abandoned vendored Click fork (~36k LOC) for a small servlet + FreeMarker stack while preserving URLs, the AJAX-validation contract, and the Security — clear
Findings
Nicely done
Recommend the render smoke tests before merge given this is the install/upgrade bootstrap path; #2–#4 are nice-to-haves. Manual fresh-install + upgrade walkthrough still worth doing. |
vharseko
left a comment
There was a problem hiding this comment.
Review — Click → Jakarta Servlets + FreeMarker migration
I focused on the real review surface (the new servlet infrastructure, the ported wizard pages and templates), checking behavior against the originals on master rather than the ~36k lines of deleted vendored Click fork.
Verdict: no blocking bugs or regressions found. This is a careful, behavior-preserving migration with good test coverage. A few cosmetic leftovers and hardening opportunities below; none are merge blockers.
What holds up well
- Reflection dispatch is safely gated.
ConfiguratorServlet.findActiononly resolves methods annotated with@ConfiguratorAction, so a request cannot invoke an arbitrary public method. UnknownactionLink→ 404, unregistered path → 404. - FreeMarker's null-strictness is handled systematically. Velocity rendered a missing
$fooas a literal; FreeMarker${foo}withRETHROW_HANDLERthrows. Pages either always populate values (getAttribute(..., default)) or templates guard with${x!""}/<#if x??>. The trickiest page,step3.ftl(${type},<#if store.password??>), is covered because both come fromLDAPStoreWizardPage.onInit()(type="config", andstoreis never null viaensureConfig()). This is backstopped byConfiguratorServletRenderSmokeTest, which renders all 10 pages and asserts no exception. - Security posture is faithfully ported.
ProtectedSetupPage.onSecurityCheck()reproduces the oldProtectedPagere-entry guard (AMSetupServlet.isConfigured());Optionsdeliberately extendsSetupPagewithout a guard, matching the oldOptions extends TemplatedPage. - Routing / downstream registration are clean.
web.xmlswapsclick-servlet→configurator-servleton*.htm;Upgrade(inopenam-upgrade) self-registers viaServiceLoader/META-INF/services(same idiom asSetupListener), avoiding a Maven cycle. No dangling references to the removedopenidentityplatform.openam.clickpackage remain in live code. - Prior review rounds already fixed real issues (NPE in
checkPasswords, JSON escaping viaJSONObject,TemplateExceptionHandler).
Minor cleanup (worth doing in this PR)
- Stale WAR manifests.
fam-console.list#L1239andfam-noconsole.list#L1334still listWEB-INF/click.xmlandWEB-INF/classes/click-page.properties, which this PR deletes. The build doesn't break (only the legacy Antsrc/main/previous_scripts/build.xmlreads these), but the lists are now inaccurate. - Stale docs.
chap-endpoints.adoc/chap-endpoints.xmlstill describe theclick-servletendpoint. copyPublicFieldsprecedence.ConfiguratorServlet.copyPublicFieldsruns after the model is seeded fromgetModel(), so a public field silently overrides anaddModelentry of the same name. Harmless today (values coincide), but a latent footgun — a comment would help.
Observations — pre-existing, not introduced by this PR (no action required here)
- No FreeMarker HTML auto-escaping. Form values (
serverURL,configStoreHost,rootSuffix, LDAP hosts,step3.ftlpassword intovalue=, and thestep7summary) render unescaped — exactly as the old Velocity$foodid. Not a regression, and these pages run pre-configuration. But since this is a rewrite it's a natural place to considerHTMLOutputFormat. Caveat: it can't be a blind flip — some templates intentionally inject HTML built in Java (e.g.Step2'sinitialCheck), which would need?no_esc. Reasonable to defer to a follow-up. validateInputsets an arbitrary session attribute from requestkey/value— a faithful port of the oldAjaxPage. SeeSetupPage.validateInput. Low risk within the setup wizard's trust model.- Dead
pushConfig/testNewInstanceUrlaction paths.wizard.ftl#L157still callsactionLink=pushConfig, but no handler exists → 404. The oldWizard.javaonly hadActionLinkfields pointing at methods that were never defined (onlycreateConfig()had a body), so this path was already broken (Click would have errored). Carried over as-is; a 404 now instead of a 500. step7summaryembeddedcheck.step7.ftl#L24<#if embedded??>is never true becauseStep7.java#L57addsisEmbedded, notembedded— but the oldstep7.htmhad the identical#if($embedded)vsadd("isEmbedded",...)mismatch. Faithful port of a latent bug.
…om fam-console.list and fam-noconsole.list
…lect changes in servlet association
…clarify behavior and edge cases
… of admin and JMX ports
Review feedback addressed — review 4656516610 @vharsekoThe review found no blocking bugs or regressions and flagged 3 minor cleanups plus 4 pre-existing observations it considered optional. All seven are now resolved on this branch (
Observations #4–#7 were pre-existing (faithful ports of the old Click/Velocity behavior), not regressions introduced by this PR — they were fixed here anyway. Also included: 2fd719c fixes a setup-IT race (waits for the config link to be enabled before clicking) in Ready for review after successful checks |
vharseko
left a comment
There was a problem hiding this comment.
Review: Migrate configurator/upgrade wizard from Apache Click to Jakarta Servlets + FreeMarker
Overview
The PR replaces the abandoned, vendored Apache Click fork (~36k LOC) that powered the 13-page configurator/upgrade wizard with a small purpose-built stack: ConfiguratorServlet (explicit path→page registry + @ConfiguratorAction reflection dispatch), a SetupPage/ProtectedSetupPage base-class pair ported from AjaxPage/ProtectedPage, a Jakarta-compatible FreeMarker TemplateLoader, and .ftl ports of every wizard template. Downstream modules (openam-upgrade) self-register pages via ServiceLoader, avoiding a Maven cycle. URLs, form field names, and actionLink parameter names are preserved byte-for-byte.
Verdict: high-quality migration — approve after fixing the copyright-header issues and deciding on fam-noconsole.list. Every migrated page class was cross-checked against its merge-base original, every .ftl against its .htm, all 28 ?no_esc sites, all actionLink names against @ConfiguratorAction methods, and the FreeMarker missing-variable surface — no correctness regressions found in reachable code. The code is unusually well-commented about why parity decisions were made.
Issues to fix before merge
1. Copyright/provenance — dropped upstream notices (CDDL compliance).
SetupPage.javais presented as a new file with onlyCopyright 2026 3A Systems LLC., but it ports substantial verbatim logic (validateInput,checkPasswords,setLocale,getBaseDir,getLocalizedString, …) fromAjaxPage.java, whose header readCopyright (c) 2007 Sun Microsystems Inc.+Portions Copyrighted 2011-2016 ForgeRock AS.under an explicit "DO NOT ALTER OR REMOVE COPYRIGHT NOTICES" banner. The Sun/ForgeRock lines must be carried over.ProtectedSetupPage.javais a rename ofProtectedPage.java(git similarity 53%) that replacedCopyright 2014 ForgeRock AS.with the 3A Systems line instead of adding a Portions line.- Minor same-family items:
step7.ftlis a new file with no license header at all;upgrade.ftlwas substantially modified but gained no 3A Systems Portions line;DefaultSummary.javaheader year not extended (2025→ should be2025-2026).
2. fam-noconsole.list is now internally inconsistent (medium, with a fossil caveat). It still lists the deleted ./config/wizard/step*.htm, ./config/options.htm, ./config/defaultSummary.htm, adds no WEB-INF/templates/config/*.ftl entries and no freemarker jar — while its web.xml was updated to map *.htm → ConfiguratorServlet. A createwar noconsole WAR would 500 on every configurator page (TemplateNotFound). Caveat: the whole list is OpenSSO-era fossil content (opensso.jar, velocity-1.7.jar), so the pipeline is likely already dead — either make the list consistent or leave the file untouched as a fossil, but don't half-update it.
3. Dangling references. Shipped javadoc/comments point at docs/migration/click-to-freemarker/03-migration-plan.md / 04-implementation-notes.md (e.g. ConfiguratorServlet.java:55, Step3.java:163, Step6.java:63, several tests) — no docs/ directory exists on the branch. Also legal/THIRDPARTYREADME.txt still attributes the removed click-nodeps-2.3.0.jar/click-extras-2.3.0.jar.
Security — net improvement
- Closes real stored-XSS sinks.
HTMLOutputFormatauto-escaping replaces Click/Velocity's raw output; merge-base templates echoed session-writable values (configStoreHost,rootSuffix,encryptionKey,serverURL,store.password, step7 summary) unescaped into attributes. All 28?no_escsites were audited: every one is server-generated markup or a bundle string that legitimately contains HTML — none carry request-controlled data. Localized strings in JS contexts correctly use?js_string?no_esc. No injection path found. validateInputis now allowlisted to the 8 session keys the wizard JS actually sends. The oldAjaxPagelet a hand-crafted?actionLink=validateInput&key=...&value=...write any session attribute — includingADMIN_PWD, bypassingcheckPasswordsvalidation. Verified complete against every template caller; no shipped functionality blocked.@ConfiguratorActionnarrows the dispatch surface — only annotated methods are reachable, vs Click's public-field ActionLinks.- Nit: overriding
service()means every HTTP method (PUT, DELETE, TRACE…) runs the full lifecycle including action dispatch; old ClickServlet exposed only GET/POST. Consider rejecting non-GET/POST/HEAD.
Correctness / parity (verified, not just sampled)
- Every old
ProtectedPagesubclass now extendsProtectedSetupPage;OptionsandUpgradecorrectly stay unprotected (required for the upgrade path on configured installs). Guard behavior (empty 200) is identical. - All action bodies are byte-equivalent apart from the
setPath(null)→skipRender()translation, which is semantically identical; lifecycle order (onSecurityCheck→onInit→ action/onGet) matches Click, includingWizard's eager port-scan initializers correctly moved toonInit(). - FreeMarker
RETHROW_HANDLER+ missing-variable audit: every unconditional${var}is unconditionally populated; every conditional value is??/!""-guarded (including the portedselectLDAPv3opendsODSEE quirk) — no render-time 500 risk found. - Two deliberate, tested behavior deltas worth noting in the PR description: step7 now actually renders the embedded-store Admin/JMX port rows (the old template guarded on a never-set
$embeddedkey — a fix, covered both directions by the render smoke test), and the deadpushConfigdialog (whose backing methods never existed at merge base) was dropped fromwizard.ftl. - Only reachable-in-theory regression:
step4.htm?actionLink=clearStore(viawizard.ftl'sdisableCustomConfig, which has zero callers) now 404s instead of harmlessly rendering. Dead code today.
Build & tests
org.freemarker:freemarkeris version-managed in the root pom (2.3.31, pre-existing) and already shipped in the WAR viaopenam-server-only; no pom still references Click;click.versionand the javadoc package excludes are cleaned up.- Tests fit the modules' conventions (TestNG + Mockito + AssertJ, all versions already managed; no new deps). Coverage is substantive, not mock-theater:
ConfiguratorServletTestdrives realservice()against the real registry (including the allowlist refusals and 404 paths);ConfiguratorServletRenderSmokeTestrenders the actual.ftlfiles and asserts the XSS-escaping,?no_esc,?js_string, and step7 toggle behavior;ConfiguratorServletUpgradeRoutingTestis the only genuine end-to-end proof of theServiceLoaderwiring. Weak spots are honest and documented (OptionsTest/DefaultSummaryTestnear-tautological due toAMSetupServletcoupling). - Test nits:
Step7Test:100comment contradicts the shipped template (the port did fix the$embeddedmismatch);ConfiguratorServletUpgradeRoutingTest:77still mocks aclick-servletnamed dispatcher nothing uses.
Suggested follow-ups (non-blocking)
- Fix the stale
SetupPagecomment claiming DefaultSummary is "not yet migrated". checkPasswordswith a missingtypeparam now silently stores the admin password where old code NPE'd — consider rejecting instead (hand-crafted requests only).- The PR's "manual wizard walkthrough recommended pre-merge" note stands: fresh-install + upgrade flow, since this is the pre-configuration bootstrap path and the render smoke test can't cover the live
AMSetupServlet.processRequesthandoff.
Summary
Replaces the vendored Apache Click fork powering the OpenAM configurator/upgrade wizard
with plain Jakarta Servlets + FreeMarker templates, removing ~36k LOC of dead/forked
framework code while preserving all existing URLs, AJAX validation behavior, and backend
coupling (
AMSetupServlet,UpgradeServices, OpenDJ).Why
fork of it just for this 13-page wizard.
replacement had to be lightweight and runtime-independent — FreeMarker is already a
managed dependency (used by
openam-oauth2) and pairs cleanly with plain Jakarta servlets.architecture and byte-for-byte URLs, just swap the framework underneath.
What changed
ConfiguratorServlettakes over the wizard's*.htmrouting via a migrated-pageregistry, so migrating a page is a Java-only change (no per-page
web.xmledits).ported from Click templates to FreeMarker
.ftltemplates underWEB-INF/templates/config/....UpgradePageProvider/Upgrade.javaupdated for the new routing.click.xml,click-page.properties,error.htm,not-found.htm, legacystep7.htm.pom.xmlupdated to drop the Click dependency.WizardTest,ConfiguratorServletTest,ServletContextTemplateLoaderTest,UpgradeTest,ConfiguratorServletUpgradeRoutingTest.and FreeMarker running side-by-side (unmigrated pages delegated to the old Click fork via
RequestDispatcher), so the reactor stayed green throughout. Click is fully removed onlyin the final increment.
Testing
upgrade path.
touches the pre-configuration bootstrap path.